# Copyright (c) 2019 Presto Labs Pte. Ltd.
# Author: taekwon(originally from coinone_v2/kr_rest/private_client.py)

import dateutil.parser

from coin.exchange.base.kr_rest.private_client_base import PrivateClientBase
from coin.exchange.gopax.kr_rest.product import GopaxProduct, GopaxCurrency

import coin.proto.coin_order_enums_pb2 as coin_order_pb2
from coin.proto.coin_query_pb2 import (
    AccountBalance,
    CurrencyBalance,
    ProductFillElement,
    ProductFillBundle,
    ProductOrderElement,
    ProductOrderBundle,
)
from coin.exchange.gopax.kr_rest.native_private_client import (
    GopaxNativePrivateClient,
    is_success_response_msg,
)


def gopax_order_side_to_proto(order_side):
  if order_side == 'sell':
    return coin_order_pb2.SELL_ORDER
  elif order_side == 'buy':
    return coin_order_pb2.BUY_ORDER
  else:
    raise ValueError('Unexpected fill type" %s' % order_side)


class GopaxPrivateParser(object):
  @staticmethod
  def parse_balance(update_msg):
    """
    [
      {
        asset: 'KRW',
        avail: 10000000000,
        hold: 5000000,
        pendingWithdrawal: 5000000
      },
      ...
    ]
    """
    currency_balance_list = []
    for elem in update_msg:
      currency = GopaxCurrency.FromStrNativeCurrencyNothrow(elem['asset'])
      if currency is None:
        continue

      available = float(elem['avail'])
      hold = float(elem['hold'])
      total = available + hold
      currency_balance = CurrencyBalance(
          currency=currency.currency,
          currency_native=currency.native_currency,
          total=total,
          available=available,
          hold=hold,
      )
      currency_balance_list.append(currency_balance)

    return AccountBalance(exchange='Gopax', each_balance=currency_balance_list)

  @staticmethod
  def parse_fill(msg, product):
    """
    [
       {
          'id':16137595,
          'orderId':211057945,
          'baseAmount':0.5,
          'quoteAmount':2170,
          'fee':0,
          'price':4340,
          'timestamp':'2019-08-21T07:39:51.000Z',
          'side':'sell',
          'tradingPairName':'EOS-KRW'
       },
       {
          'id':16122734,
          'orderId':210655251,
          'baseAmount':5,
          'quoteAmount':21885,
          'fee':4.377,
          'price':4377,
          'timestamp':'2019-08-20T12:29:25.000Z',
          'side':'sell',
          'tradingPairName':'EOS-KRW'
       }
    ]
    """
    assert is_success_response_msg(msg), msg
    currency_fill_list = []

    for update_msg in msg:
      order_id = str(update_msg['orderId'])
      trade_id = str(update_msg['id'])
      side = gopax_order_side_to_proto(update_msg['side'])
      timestamp = int(dateutil.parser.parse(update_msg['timestamp']).timestamp() * 1e9)
      price = update_msg['price']
      qty = update_msg['baseAmount']
      fill_id = '%s_%s_%s' % (product.symbol, order_id, trade_id)
      currency_fill = ProductFillElement(symbol=product.symbol,
                                         price=float(price),
                                         qty=float(qty),
                                         fee=float(update_msg['fee']),
                                         order_id=order_id,
                                         exchange_order_id=order_id,
                                         fill_id=fill_id,
                                         fill_timestamp=timestamp,
                                         side=side)
      currency_fill_list.append(currency_fill)

    return ProductFillBundle(exchange='Gopax', symbol=product.symbol, each_fill=currency_fill_list)

  @staticmethod
  def parse_orders(msg, product):
    """
    [
     {
        'id':'210655331',
        'status':'placed',
        'side':'buy',
        'type':'limit',
        'price':4000,
        'amount':1.5,
        'remaining':1.5,
        'tradingPairName':'EOS-KRW',
        'createdAt':'2019-08-20T12:29:38.000Z',
        'updatedAt':'2019-08-20T12:29:38.000Z'
     }
    ]
    """
    order_list = []
    for update_elem in msg:
      order_id = str(update_elem['id'])
      created_timestamp = int(dateutil.parser.parse(update_elem['createdAt']).timestamp() * 1e9)
      currency_order = ProductOrderElement(symbol=product.symbol,
                                           price=float(update_elem['price']),
                                           qty=float(update_elem['amount']),
                                           order_id=order_id,
                                           exchange_order_id=order_id,
                                           created_timestamp=created_timestamp,
                                           side=gopax_order_side_to_proto(update_elem['side']))
      order_list.append(currency_order)
    return ProductOrderBundle(
        exchange='Gopax',
        symbol=product.symbol,
        each_order=order_list,
    )


class GopaxPrivateClient(PrivateClientBase):
  ProductType = GopaxProduct

  def __init__(self, key_file):
    super().__init__()
    self.nprivc = GopaxNativePrivateClient(key_file=key_file)

  def query_account_balance_impl(self):
    update = self.nprivc.query_account_balance()
    update.msg = GopaxPrivateParser.parse_balance(update.msg)
    return update

  def query_list_orders_impl(self, product):
    currency = product.native_symbol
    update = self.nprivc.query_orders(currency)
    update.msg = GopaxPrivateParser.parse_orders(update.native_msg, product)
    return update

  def query_fills_impl(self, product):
    currency = product.native_symbol
    update = self.nprivc.query_trades(currency=currency)
    update.msg = GopaxPrivateParser.parse_fill(update.msg, product)
    return update

  def query_submit_impl(self, product, side, price, qty):
    raise NotImplementedError()

  def query_cancel_impl(self, product, order_id):
    raise NotImplementedError()

  def query_cancel_product_impl(self, product):
    raise NotImplementedError()

  def query_account_list_orders_impl(self):
    raise NotImplementedError()

  def query_account_fills_impl(self):
    raise NotImplementedError()

  def query_account_position_impl(self):
    raise NotImplementedError()
